/*    This file is part of rls.
 *
 *    Rls is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Rls is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with rls.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ctype.h>
#include <endian.h>
#include <inttypes.h>
#include <rid.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STYLE_9
#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <unfy/unfp.h>

#include "rls.h"
#include "rlsp.h"

/* length of constant size data for rule */
static const size_t core_len = sizeof(Rid) + 2 * sizeof(uint16_t) +
	sizeof(uint8_t);

static Rls_rule *
get_rule(Rls_recycle *rec)
{
	Rls_rule *rule = rec->rules;

	if (!rule)
		return malloc(sizeof(*rule));

	rec->rules = rule->next;
	return rule;
}

static Rls_srch *
get_srch(Rls_recycle *rec)
{
	Rls_srch *srch = rec->srchs;

	if (!srch)
		return malloc(sizeof(*srch));

	rec->srchs = (void *) srch->body;
	return srch;
}

/* rule */

void
rlsp_rule_print(const Rls_rule *rule, Rlsp_dat_print dat_print)
{
	if (!rule) {
		printf("Empty\n");
		return;
	}

	for (; rule; rule = rule->next) {
		Rid_fn85 str;

		rid_fn85(str, rule->id);
		printf("%s", str);

		if (dat_print)
			dat_print(rule->dat);

		printf(" ");
		unfp_term_print(rule->head);
		printf(" ");

		if (rule->actable)
			printf("ACT");
		else
			unfp_list_print(rule->u.body);

		printf("\n");
	}
}

size_t
rlsp_rule_len(const Rls_rule *rules, Rlsp_srlz *srlz, void *extra)
{
	size_t len = 2;

	if (!rules)
		return 1;

	for (; rules; rules = rules->next) {
		len += 1 + core_len + unfp_term_len(rules->head);

		if (srlz)
			len += srlz->len(rules, extra);

		if (!rules->actable)
			len += unfp_list_len(rules->u.body);
	}

	return len;
}

size_t
rlsp_rule_write(const Rls_rule *rules, unsigned char *buf,
		Rlsp_srlz *srlz, void *extra)
{
	size_t len = 1;

	if (!rules) {
		*buf = RLSP_RNIL;
		return 1;
	}

	*buf = RLSP_ROPEN;

	for (; rules; rules = rules->next) {
		*(buf + len) = RLSP_RULE;
		++len;
		rid_set(buf + len, rules->id);
		len += sizeof(rules->id);
		*(uint16_t *)(buf + len) = htole16(rules->size);
		len += sizeof(rules->size);
		*(uint16_t *)(buf + len) = htole16(rules->max_binds);
		len += sizeof(rules->max_binds);
		*(buf + len) = rules->actable;
		len += sizeof(rules->actable);
		len += unfp_term_write(rules->head, buf + len);

		if (srlz)
			len += srlz->write(rules, buf + len, extra);

		if (!rules->actable)
			len += unfp_list_write(rules->u.body, buf + len);
	}

	buf[len] = RLSP_RCLOSE;
	return len + 1;
}

int
rlsp_rule_ser(const Rls_rule *rules, unsigned char **buf, size_t *size,
	      size_t *pos, Rlsp_srlz *srlz, void *extra)
{
	size_t len = rlsp_rule_len(rules, srlz, extra);

	if (*size < len) {
		unsigned char *new_buf = realloc(*buf, len);

		if (!new_buf)
			return -1;

		*buf = new_buf;
		*size = len;
	}

	rlsp_rule_write(rules, *buf, srlz, extra);
	*pos += len;
	return 0;
}

int
rlsp_rule_deser(Rls_rule **rules, const unsigned char *buf, size_t *pos,
		Rlsp_srlz *srlz, void  *extra, Rls_recycle *rec)
{
	Rls_rule **end = rules;
	Rls_dat_free dat_free = srlz ? srlz->dat_free : NULL;

	*rules = NULL;

	if (*buf == RLSP_RNIL) {
		++*pos;
		return 0;
	}

	if (*buf != RLSP_ROPEN)
		return -1;

	++*pos;
	++buf;

	do {
		size_t tmp = 1;

		if (*buf != RLSP_RULE || !(*end = get_rule(rec))) {
			rls_recycle_rule(rec, *rules, dat_free, extra);
			*rules = NULL;
			return -1;
		}

		(*end)->next = NULL;
		rid_set((*end)->id, buf + tmp);
		tmp += sizeof((*end)->id);
		(*end)->size = le16toh(*(uint16_t *)(buf + tmp));
		tmp += sizeof((*end)->size);
		(*end)->max_binds = le16toh(*(uint16_t *)(buf + tmp));
		tmp += sizeof((*end)->max_binds);
		(*end)->actable = *(buf + tmp);
		tmp += sizeof((*end)->actable);

		if (unfp_term_deser(&(*end)->head, buf + tmp,
				    &tmp, &rec->unfy) < 0) {
			*pos += tmp;
			goto err;
		}

		*pos += tmp;
		buf += tmp;
		tmp = 0;

		if (srlz && srlz->deser(*end, buf, &tmp, extra) < 0)
			goto err;

		if (!(*end)->actable &&
		    unfp_list_deser(&(*end)->u.body, buf + tmp,
				    &tmp, &rec->unfy) < 0)
				goto err;

		*pos += tmp;
		buf += tmp;
		end = &(*end)->next;
	} while (*buf != RLSP_RCLOSE);

	++*pos;
	return 0;
err:
	/* This bit is a little hackish, basically makes this
	   unfinished rule recyclable */
	(*end)->actable = 1;
	rls_recycle_rule(rec, *rules, dat_free, extra);
	*rules = NULL;
	return -1;
}

const char *
rlsp_rule_deserable(const unsigned char *buf, size_t *pos,
		    const size_t max, Rlsp_srlz *srlz, void *extra)
{
	const char *error;

	if (!buf || *pos >= max)
		return "Not enough space for rule";

	switch (*buf) {
	case RLSP_RNIL:
		++*pos;
		return NULL;
	case RLSP_ROPEN:
		break;
	default:
		return "Missing rule byte";
	}

	++*pos;
	++buf;

	do {
		size_t tmp = 0;
		uint8_t actable;

		if (*buf != RLSP_RULE)
			return "Missing rule byte";

		++*pos;
		++buf;

		if (*pos + core_len > max) {
			*pos = max;
			return "Content of rule exceeds maximum";
		}

		tmp = (*pos += core_len);
		buf += core_len;
		actable = buf[-1];

		if ((error = unfp_term_deserable(buf, pos, max)))
			return error;

		buf += *pos - tmp;
		tmp = *pos;

		if (srlz && (error = srlz->deserable(buf, pos, max, extra)))
			return error;

		buf += *pos - tmp;
		tmp = *pos;

		if (!actable && (error = unfp_list_deserable(buf, pos, max)))
			return error;

		buf += *pos - tmp;

		if (*pos + 1 > max)
			return "Missing rule close byte";
	} while (*buf != RLSP_RCLOSE);

	++*pos;
	return NULL;
}

/* srch */

static void
indent_level(int level)
{
	for (int i = 0; i < level; ++i)
		printf("\t");
}

static void
binds_flow_print(const Rls_srch *srch, int level)
{
	Unfy_bind *bind = srch->nbind;

	indent_level(level);
	printf("Left\n");

	for (; bind; bind = bind->next) {
		Unfy_term *lterm = unfy_bind_get(srch->lbind, bind->term->u.id);
		Unfy_term *fterm = unfy_bind_get(srch->fbind, bind->var_id);

		indent_level(level + 1);
		unfp_bind_print(bind);

		if (lterm) {
			printf(" -> ");
			unfp_term_print(lterm);
		}

		if (fterm) {
			printf(" -> ");
			unfp_term_print(fterm);
		}

		printf("\n");
	}

	indent_level(level);
	printf("RIGHT\n");
	unfp_binds_print(srch->rbind, level + 1);
}

void
rlsp_srch_print(const Rls_srch *srch, int level)
{
	for (int i = 0; i < level; ++i)
		printf("\t");

	if (!srch) {
		printf("Empty\n");
		return;
	}

	printf("%" PRIu16 ".", srch->num);

	unfp_term_print(srch->term);
	printf("\n");
	binds_flow_print(srch, level + 1);

	if (!srch->rule->actable && srch->body)
		for (int i = 0; i < srch->rule->size; ++i)
			rlsp_srch_print(srch->body[i], level + 1);
}

size_t
rlsp_srch_len(const Rls_srch *srch)
{
	size_t len = 1 + sizeof(srch->rule->id) + sizeof(srch->num) +
		sizeof(srch->rule->size);

	if (!srch)
		return 1;

	len += unfp_term_len(srch->term) + unfp_bind_len(srch->lbind) +
		unfp_bind_len(srch->rbind) + unfp_bind_len(srch->nbind) +
		unfp_bind_len(srch->fbind);

	for (int i = 0; i < srch->rule->size; ++i)
		len += rlsp_srch_len(srch->body[i]);

	return len;
}

size_t
rlsp_srch_write(const Rls_srch *srch, unsigned char *buf)
{
	size_t len = 1;

	if (!srch) {
		*buf = RLSP_SNIL;
		return 1;
	}

	*buf = RLSP_SOPEN;
	rid_set(buf + len, srch->rule->id);
	len += sizeof(Rid);
	*(uint16_t *)(buf + len) = htole16(srch->num);
	len += sizeof(srch->num);
	*(uint16_t *)(buf + len) = htole16(srch->rule->size);
	len += sizeof(srch->rule->size);
	len += unfp_term_write(srch->term, buf + len);
	len += unfp_bind_write(srch->lbind, buf + len);
	len += unfp_bind_write(srch->rbind, buf + len);
	len += unfp_bind_write(srch->nbind, buf + len);
	len += unfp_bind_write(srch->fbind, buf + len);

	for (int i = 0; i < srch->rule->size; ++i)
		len += rlsp_srch_write(srch->body[i], buf + len);

	return len;
}

int
rlsp_srch_ser(const Rls_srch *srch, unsigned char **buf,
	      size_t *size, size_t *pos)
{

	size_t len = rlsp_srch_len(srch);

	if (*size < len) {
		unsigned char *new_buf = realloc(*buf, len);

		if (!new_buf)
			return -1;

		*buf = new_buf;
		*size = len;
	}

	rlsp_srch_write(srch, *buf);
	*pos += len;
	return 0;
}

const char *
rlsp_srch_deser(Rls_srch **srch, const unsigned char *buf, size_t *pos,
		Rls_rule *rules, Rls_bend bend, void *extra, Rls_recycle *rec)
{
	size_t len;
	Rid rule_id;
	uint16_t num;
	uint16_t size;
        Rls_rule *rule = rules;
	Unfy_term *term;
	const char *error;

	if (*buf == RLSP_SNIL) {
		*srch = NULL;
		++*pos;
		return NULL;
	}

	if (*buf != RLSP_SOPEN) {
		*srch = NULL;
		return "Missing srch byte";
	}

	len = 1;
	rid_set(rule_id, buf + len);
	len += sizeof(rule_id);
	num = le16toh(*(uint16_t *)(buf + len));
	len += sizeof(num);
	size = le16toh(*(uint16_t *)(buf + len));
	len += sizeof(size);

	if (unfp_term_deser(&term, buf + len, &len, &rec->unfy) < 0)
		return "Failed to deser term";

	if (bend) {
		Unfy_stat stat = UNFY_ERR;
		Rls_rule **tmp = bend(term, &stat, extra);

		if (!tmp) {
			/* we can't find the rule, so we can't deser */
			unfy_recycle_term(&rec->unfy, term);
			return "Failed rule bend";
		}

		rule = *tmp;
	}

	for (; rule && rid_cmp(rule->id, rule_id); rule = rule->next);

	if (!rule) {
		unfy_recycle_term(&rec->unfy, term);
		return "Rule not found";
	}


	if (!(*srch = get_srch(rec))) {
		unfy_recycle_term(&rec->unfy, term);
		return "Failed to allocate srch";
	}

	(*srch)->up = NULL;
	(*srch)->rule = rule;
	(*srch)->term = term;
	(*srch)->num = num;

	if (!size)
		(*srch)->body = NULL;
	else if (!((*srch)->body = calloc(size, sizeof(Rls_srch *)))) {
		error = "Failed to allocate body";
		goto body_err;
	}

	if (unfp_bind_deser(&(*srch)->lbind, buf + len, &len, &rec->unfy) < 0) {
		error = "Failed to deser lbind";
		goto lbind_err;
	}

	if (unfp_bind_deser(&(*srch)->rbind, buf + len, &len, &rec->unfy) < 0) {
		error = "Failed to deser rbind";
		goto rbind_err;
	}

	if (unfp_bind_deser(&(*srch)->nbind, buf + len, &len, &rec->unfy) < 0) {
		error = "Failed to deser nbind";
		goto nbind_err;
	}

	if (unfp_bind_deser(&(*srch)->fbind, buf + len, &len, &rec->unfy) < 0) {
		error = "Failed to deser fbind";
		goto full_err;
	}

	for (int i = 0; i < size; ++i) {
		if ((error = rlsp_srch_deser(&(*srch)->body[i], buf + len,
					     &len, rules, bend, extra, rec)))
			goto full_err;

		(*srch)->body[i]->up = *srch;
	}

	*pos += len;
	return NULL;
body_err:
	(*srch)->lbind = NULL;
lbind_err:
	(*srch)->rbind = NULL;
rbind_err:
	(*srch)->nbind = NULL;
nbind_err:
	(*srch)->fbind = NULL;
full_err:
	rls_recycle_srch(rec, *srch);
	*srch = NULL;
	return error;
}

const char *
rlsp_srch_deserable(const unsigned char *buf, size_t *pos, const size_t max)
{
	const char *error;
	uint16_t size;
	size_t tmp;

	if (!buf || *pos >= max)
		return "Not enough space for srch";

	switch (*buf) {
	case RLSP_SNIL:
		++*pos;
		return NULL;
	case RLSP_SOPEN:
		break;
	default:
		return "Missing srch byte";
	}

	++*pos;
	++buf;

	if (*pos + sizeof(Rid) + 2 * sizeof(uint16_t) > max) {
		*pos = max;
		return NULL;
	}

	*pos += sizeof(Rid) + 2 * sizeof(uint16_t);
	buf += sizeof(Rid) + 2 * sizeof(uint16_t);
	size = ((uint16_t *) buf)[-1];
	tmp = *pos;

	if ((error = unfp_term_deserable(buf, pos, max)))
		return error;

	for (int i = 0; i < 4; ++i) {
		buf += *pos - tmp;
		tmp = *pos;

		if ((error = unfp_bind_deserable(buf, pos, max)))
			return error;
	}

	for (uint16_t i = 0; i < size; ++i) {
		buf += *pos - tmp;
		tmp = *pos;

		if ((error = rlsp_srch_deserable(buf, pos, max)))
			return error;
	}

	return NULL;
}
